Karolina Seweryn
Celem pracy domowej jest wyjaśnienie predykcji modelu na poziomie instacji. Modelowanie dotyczy predykcji cen doby hotelowej na podstawie zbioru Hotel Booking Demand.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
from sklearn.ensemble import RandomForestRegressor
from sklearn.feature_selection import RFE
from lime.lime_tabular import LimeTabularExplainer
from xgboost import XGBRegressor
df = pd.read_csv("./data/hotel_bookings.csv")
def get_integer_mapping(le):
'''
Return a dict mapping labels to their integer values.
le: a fitted sklearn LabelEncoder
'''
res = {}
for cl in le.classes_:
res.update({cl:le.transform([cl])[0]})
return res
df = df.drop(["arrival_date_year", "country", "agent", "company", "reservation_status_date", "reservation_status", "is_canceled"], axis=1)
feature_type = df.dtypes
object_features = [i for i in feature_type.index if feature_type[i] == 'object']
for feat in object_features:
le = LabelEncoder()
df[feat] = le.fit_transform(df[feat])
integerMapping = get_integer_mapping(le)
print(feat)
print(integerMapping)
X = df.drop('adr', axis=1)
X = X.fillna(value=0)
y = df['adr']
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=123)
df.shape
Pierwszym modelem będzie model drzewiasty Random Forest z selekcją zmiennych RFE (Recursive Feature Elimination), dla której jako model na podstawie, którego dokonuje selekcji wybrałam mniejszy model niż do finalnej predykcji.
rf_sel = RandomForestRegressor(max_depth=8, n_estimators=100)
selector = RFE(rf_sel, 15, step=2)
selector.fit(X_train, y_train)
cols = selector.get_support(indices=True)
X_train, X_test = X_train.iloc[:,cols], X_test.iloc[:,cols]
rf = RandomForestRegressor(max_depth=15, n_estimators=500)
rf.fit(X_train, y_train)
y_pred_train = rf.predict(X_train)
y_pred_test = rf.predict(X_test)
print("R2 score \ntrain: {}, \ntest: {}.".format(r2_score(y_train, y_pred_train), r2_score(y_test, y_pred_test)))
print("MSE \ntrain: {}, \ntest: {}.".format(mean_squared_error(y_train, y_pred_train), mean_squared_error(y_test, y_pred_test)))
print("MAE \ntrain: {}, \ntest: {}.".format(mean_absolute_error(y_train, y_pred_train), mean_absolute_error(y_test, y_pred_test)))
Do wyjaśnienia predykcji na poziomie instancji i odpowiedzi na pytania dotyczące zmiennych wpływających na wartość predykcji użyję metody LIME.
exp = LimeTabularExplainer(X_train.values, mode='regression',
feature_names=X_train.columns.tolist(),
class_names=['price'],
categorical_features=object_features,
verbose=True)
exp.explain_instance(X_train.loc[71218, :], rf.predict, num_features=8).show_in_notebook(show_table=True)
Powyższy wykres przedstawia wyjaśnienie predykcji dla pojedynczej obserwacji o numerze 71218. Typ hotelu (hotel) oraz numer tygodnia przyjazdu (arrival_date_week_number) zwiększają cenę za dobę hotelową, zaś wartości zmiennych deposit_type, reserved_room_type, booking_changes, meal, adults, children zmniejszają cenę. Ostatecznie predykcja dla tej obserwacji wynosi $108.98$.
exp.explain_instance(X_train.loc[117727, :], rf.predict, num_features=8).show_in_notebook(show_table=True)
Powyższy wykres przedstawia wyjaśnienie predykcji dla pojedynczej obserwacji o numerze 117727. Typ hotelu (hotel) oraz numer tygodnia przyjazdu (arrival_date_week_number) zwiększają cenę za dobę hotelową, zaś wartości zmiennych reserved_room_type, deposit_type, meal, market_segment, adults, booking_changes zmniejszają cenę. Ostatecznie, model przewidział cenę dla tej obserwacji równą $85.75$.
Wyjaśnienie predykcji dla obserwacji, dla której model przewidział największą cenę
w = rf.predict(X_train)
ind = X_train.index.to_list()[np.argmax(w)]
exp.explain_instance(X_train.loc[ind, :], rf.predict, num_features=10).show_in_notebook(show_table=True)
Powyższy wykres przedstawia wyjaśnienie predykcji dla pojedynczej obserwacji, dla której model przewidział najwzyższy wynik w zbiorze treningowym (3276.50). Można zauważyć, że na podwyższenie ceny doby hotelowej mają wpływ wartości zmiennych hotel, deposit_type, booking_changes, lead_time, a obniżają cenę wartości zmiennych arrival_date_week_number, reserved_room_type, meal, adults, childer, market_segment. Klient ten dokonał 1 miany w rezerwacji, więc zgodnie z logiką ta wartość wpływa na zwiększenie ceny. Wybór opcji jedynie ze śniadaniami zmniejszył cenę. Rezerwacja została dokonana dla 2 dorosłych bez dzieci, co również zmniejszyło cenę.
xgb = XGBRegressor()
xgb.fit(X_train.values, y_train.values)
y_pred_train = xgb.predict(X_train.values)
y_pred_test = xgb.predict(X_test.values)
print("R2 score \ntrain: {}, \ntest: {}.".format(r2_score(y_train, y_pred_train), r2_score(y_test, y_pred_test)))
print("MSE \ntrain: {}, \ntest: {}.".format(mean_squared_error(y_train, y_pred_train), mean_squared_error(y_test, y_pred_test)))
print("MAE \ntrain: {}, \ntest: {}.".format(mean_absolute_error(y_train, y_pred_train), mean_absolute_error(y_test, y_pred_test)))
exp.explain_instance(X_train.loc[71218, :], xgb.predict, num_features=8).show_in_notebook(show_table=True)
Powyższy wykres przedstawia wyjaśnienie predykcji dla obserwacji o indeksie 71218 i modelu XGBoost. Możemy zauważyć, że w wyjaśnieniu tej obserwacji w poprzednim modelu nie znajdowała się zmienna market_segment.
exp.explain_instance(X_train.loc[117727, :], xgb.predict, num_features=8).show_in_notebook(show_table=True)
Dla tej obserwacji również możemy zauważyć różnicę. W modelu RF piątą zmienną była zmienna adult, a w tym przypadku jest to previous_cancellations. Co ciekawe, brak wcześniejszych anulowań rezerwacji zwiększa cenę za dobę hotelową
exp.explain_instance(X_train.loc[ind, :], xgb.predict, num_features=10).show_in_notebook(show_table=True)
Powyżej przedstawiam przypadek największej wartości predykcji w przypadku obu modeli. Wyjaśnienie wartości tej predykcji w przypadku modelu Random Forest i XGBoost różni się nieznacznie. W wyjaśnieniu modelu XGBoost pojawiają się zmienne previous_cancellarions i arrival_date_day_of_month, które nie pojawiły się w wyjaśnieniu predykcji modelu Random Forest dla tej obserwacji